﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO.Ports;
using System.Threading;

namespace JCPogi.Core {
    
    public class CommunicationDevice : IDisposable {

        public static CommunicationDevice Open(string port) {
            var device = new CommunicationDevice(port);
            device.OpenPort();

            return device;
        }

        public CommunicationDevice(string port) {
            _serialPort = InitializePort(port);
        }

        public virtual DeviceCommandResponse Execute(DeviceCommand command) {
            return ExecuteCommand(command.CommandText);
        }

        public virtual DeviceCommandResponse ExecuteCommand(string command) {
            _serialPort.DiscardOutBuffer();
            _serialPort.DiscardInBuffer();
            _receiveNow.Reset();
            _serialPort.Write(string.Format("{0} \r", command));

            return CommandResponse(_serialPort);
        }

        public void Dispose() {
            _serialPort.Dispose();
            _serialPort = null;
        }

        private SerialPort InitializePort(string name, int baudRate = 9600, int dataBits = 8, StopBits stopBits = StopBits.One,
                                          Parity parity = Parity.None, int readTimeout = 300, int writeTimeout = 300) {
                
            var port = new SerialPort(name) {
                BaudRate = baudRate,
                DataBits = dataBits,
                StopBits = stopBits,
                Parity = parity,
                ReadTimeout = readTimeout,
                WriteTimeout = writeTimeout,
                Encoding = Encoding.GetEncoding("iso-8859-1"),
                DtrEnable = true,
                RtsEnable = true
            };

            port.DataReceived += new SerialDataReceivedEventHandler(OnSerialPortDataReceived);
            return port;
        }

        private void OpenPort() {
            _receiveNow = new AutoResetEvent(false);
            _serialPort.Open();            
        }

        private DeviceCommandResponse CommandResponse(SerialPort port) {
            string responseBuffer = string.Empty;

            do {
                if (_receiveNow.WaitOne()) { // wait until final device response is complete
                    string t = port.ReadExisting();
                    responseBuffer += t;
                }
                else { // shouldn't reach this point. 
                    if (responseBuffer.Length > 0)
                        throw new ApplicationException("Response received is incomplete.");
                    else
                        throw new ApplicationException("No data received from phone.");
                }
            }
            while (!DeviceCommandResponse.IsAckReceived(responseBuffer));
            return new DeviceCommandResponse(responseBuffer);
        }

        private void OnSerialPortDataReceived(object sender, SerialDataReceivedEventArgs args) {
            if (args.EventType == SerialData.Chars) {
                _receiveNow.Set();
            }
        }

        private SerialPort _serialPort;
        private AutoResetEvent _receiveNow;
    }
}
